<?php
/*
 * Created on 21.12.2005
 *
 */

m2import('phpinchains/tagsupport/functions.inc.php');
m2import('phpinchains/parser/psp/PSPLexer.php');
m2import('phpinchains/parser/el/ELLexer.php');
m2import('phpinchains/parser/el/ELCallback.php');
m2import('phpinchains/parser/el/ELGrammar.lalr.php');
m2import('phpinchains/parser/el/tokenClasses.php');
m2import('phpinchains/parser/ParserMachine.php');

class phpinchains_parser_psp_PSPParser {

    var $lexer;
    var $tokenlist;
    // stack of tags
    var $tagstack;
    // tagtree
    var $tagtree = array('name' => 'root', 'children' => array());
    var $currentNode;
    var $valstack = array();
    var $parentArray;
    var $basedir;
    var $ellexer;
    var $elparser;
    // compiled psp code
    var $appCode;
    // validation code
    var $appValCode;
    // append code for statically requestable data of tags
    var $requestableData = "";
    // js code placed in head
    var $jsCode;
    // name of currently captured form
    var $capture;
    // captured forms code
    var $captured;
    //
    var $surpressAppend = false;
    // supression of cdata
    var $surpressCdataAppend = false;
    // map with fields of each form in the psp
    var $fieldMap = array();
    // map with fields of type file of each form in the psp
    var $fileFieldMap = array();
    // Variable name stack for java style tags
    var $parentVarnames = array();
    
    /**
     * @param PSPLexer lex Reference to the PSPLexer instance 
     * @param string basedir Path to the module to load project tags
     */
    function phpinchains_parser_psp_PSPParser($lex, $basedir)
    {
        $this->lexer   = & $lex;
        $this->basedir = $basedir;
    }
    
    function & getTree () 
    {
        return $this->tagtree;
    }
    
    /**
     * Get top of tag stack
     */
    function top ()
    {
        return end($this->tagstack);
    }
    
    /**
     * Get top of value stack
     */
    function valTop ()
    {
        return end($this->valstack);
    }
    
    /**
     * Pop top of value stack
     */
    function valPop ()
    {
        array_pop($this->valstack);
    }
    
    /**
     * Do parsing
     */
    function parse ()
    {
        $this->currentNode = & $this->tagtree['root'];
        while ($this->lexer->hasNextToken())
        {
            $this->lexer->nextToken();
            $token = $this->lexer->getNextToken();
            // character data
            if ($token['type'] == 'T_CDATA')
            {
                $this->tokenlist[] = $token;
                $this->cData($token['value']);
            }
            // php scriptlet (php code block) NOTE! unlike normal php pages, the last line also has to be 
            // terminated with semicolon
            if ($token['type'] == 'T_SCRIPTLET')
            {
                $this->tokenlist[] = $token;
                $this->appendCode($token['value']);
            }
            // Expression Language code
            if ($token['type'] == 'T_EL_CODE')
            {
                // create a new EL parser and parse the block
                $this->tokenlist[] = $token;
                $lex    = new phpinchains_parser_el_ELLexer($token['value']);
                $lex->lex();
                //var_dump($lex->tokenlist);
                $parser = new phpinchains_parser_ParserMachine($lex, $GLOBALS['parsertable'], $GLOBALS['parserproductions'], $GLOBALS['tokentranslationtable']);
                $call   = new phpinchains_parser_el_ELCallback(); 
                if (!$parser->run($call))
                {
                    $this->appendCode('$out->write("<b>PSPParser:</b> Error in EL Expression in line '.$token['line'].$token['value'].' in file '.$this->lexer->filename.'!<br/>\n");');
                }
                else
                {
                    // get the AST and dump to php => EL compiled to php
                    $ast = $call->getAST();
                    $this->appendCode('$out->write('.$ast->dumpPHP().');');
                }
            }
            // Begin of an open tag < of <sometag> or <sometag/>
            elseif ($token['type'] == 'T_BEGIN_OPEN_TAG')
            {
                $tag = array();
                // check for a next token, other wise the parsed code ends with a <
                if (!$this->lexer->hasNextToken()) 
                {
                    $this->lexer->nextToken();
                    $idT = $this->lexer->getNextToken();
                    die ("<b>PSPParser:</b> Tag name expected in line" . $idT['line'] . "\n");
                }
                $this->lexer->nextToken();
                $idT = $this->lexer->getNextToken();
                // tag name
                if ($idT['type'] == 'T_IDENTIFIER')
                {
                    $this->tagstack[] = $idT['value'];
                    $tag['type']  = 'T_OPENTAG';
                    $tag['name']  = $idT['value'];
                    $tag['ns']    = $token['ns'];
                    // parse attributes
                    $tag['attrs'] = $this->attributes();
                }
                $end = $this->lexer->getNextToken();
                // End of a tag >
                if ($end['type'] == 'T_END_TAG')
                {
                    $this->tokenlist[] = $tag;
                    $this->startElement($tag['ns'], $tag['name'], $tag['attrs'], $idT['line']);
                }
                // End of a finite or non-container tag, resp. />
                elseif ($end['type'] == 'T_END_FIN_TAG')
                {
                    $this->tokenlist[] = $tag;
                    $this->startElement($tag['ns'], $tag['name'], $tag['attrs'], $idT['line']);
                    $tag = array();
                    $tag['type']  = 'T_CLOSE_TAG';
                    $tag['ns']    = $token['ns'];
                    $tag['name']  = $idT['value'];
                    array_pop($this->tagstack);
                    $this->tokenlist[] = $tag;
                    $this->endElement($tag['ns'], $tag['name']);
                }
            }
            // Begin of a closing tag </
            elseif ($token['type'] == 'T_BEGIN_CLOSE_TAG')
            {
                $tag = array();
                // check for a next token, other wise the parsed code ends with a </ 
                if (!$this->lexer->hasNextToken()) 
                {
                    $this->lexer->nextToken();
                    $idT = $this->lexer->getNextToken();
                    die ("<b>PSPParser:</b> Tag name expected in line" . $idT['line'] . "\n");
                }
                $this->lexer->nextToken();
                $idT = $this->lexer->getNextToken();
                // Tag name
                if ($idT['type'] == 'T_IDENTIFIER')
                {
                    if ($this->top() != $idT['value'])
                        die ("<b>PSPParser:</b> ".$token['ns'].":" . $this->top() . " tag expected in line " 
                            . $idT['line'] .' in file '.$this->lexer->filename . " but " . $idT['value'] . " found\n");
                    else
                        array_pop($this->tagstack);
                    $tag['type']  = 'T_OPENTAG';
                    $tag['ns']    = $token['ns'];
                    $tag['name']  = $idT['value'];
                    $this->tokenlist[] = $tag;
                    $this->endElement($tag['ns'], $tag['name']);
                }
                $this->lexer->nextToken();
                $end = $this->lexer->getNextToken();
                // End of the closing tag >
                if ($end['type'] != 'T_END_TAG')
                {
                     die ("<b>PSPParser:</b> '>' expected in line " . $end['line'] . "\n");
                }
            }
        }
        if (count($this->tagstack) > 0)
        {
            die ("<b>PSPParser:</b> ".$token['ns'].":" . $this->top() . " tag expected at line " . $this->tokenlist[count($this->tokenlist)-1]['line'].' in file '.$this->lexer->filename . "\n");
        }
    }
    
    /**
     * Parse tag attributes
     */
    function attributes ()
    {
        // if no next token parsed code ends with <{tagname}
        if ($this->lexer->hasNextToken())
        {
            $this->lexer->nextToken();
            $name = $this->lexer->getNextToken();
            $attrs = array();
            // Attribute name
            while ($name['type'] == 'T_IDENTIFIER')
            {
                list($n, $v) = $this->attribute();
                $attrs[$n] = $v;
                $this->lexer->nextToken();
                $name = $this->lexer->getNextToken();
            }
            return $attrs;
        }
        else
        {
            $name = $this->lexer->getNextToken();
            die ("<b>PSPParser:</b> Attribute, '>' or '/>' in line " . $name['line'].' in file '.$this->lexer->filename . "\n");
        }
    }
    
    /**
     * Parse next tag attribute
     */
    function attribute ()
    {
        $name   = array();
        $assign = array();
        $value  = array();
        $name = $this->lexer->getNextToken();
        // attribute name
        if ($name['type'] == 'T_IDENTIFIER')
        {
            $this->lexer->nextToken();
            $assign = $this->lexer->getNextToken();
            // assignment =
            if ($assign['type'] == 'T_ASSIGN')
            {
                $this->lexer->nextToken();
                $value = $this->lexer->getNextToken();
                // attribute value
                if ($value['type'] == 'T_VALUE')
                {
                    // attribute value may be or contain an EL expression, so parse the value
                    $lex    = new phpinchains_parser_el_ELLexer($value['value']);
                    $lex->lex();
                    $parser = new phpinchains_parser_ParserMachine($lex, $GLOBALS['parsertable'], $GLOBALS['parserproductions'], $GLOBALS['tokentranslationtable']);
                    $call   = new phpinchains_parser_el_ELCallback(); 
                    if (strlen($value['value']) == 0)
                    {
                        return array ($name['value'], '""');
                    }
                    elseif (!$parser->run($call))
                    {
                        print "<b>PSPParser:</b> Error in EL Expression in line ".$value['line'].' in file '.$this->lexer->filename."<br/>\n";
                        return array ($name['value'], '""');
                    }
                    else
                    {
                        $ast = $call->getAST();
                        if ($ast)
                            return array ($name['value'], $ast->dumpPHP());
                        else
                            return array ($name['value'], '');
                    }
                }
                else
                    die ("<b>PSPParser:</b> Attribute value surrounded by \" expected in line " . $value['line'].' in file '.$this->lexer->filename . "\n");
            }
            else
                die ("<b>PSPParser:</b> Assignment expected in line " + $assign['line'].' in file '.$this->lexer->filename . "\n");
        }
    }
    
    /**
     * Increment tag count
     */
    function incTagCount($id)
    {
        $this->tagCount[$id]++;
    }

    /**
     * Get tag count
     */
    function getTagCount($id)
    {
        if (!isset($this->tagCount[$id]))
        {
            $this->tagCount[$id] = 0;
        }
        return $this->tagCount[$id];
    }    
    
    /**
     * 
     */
    function pushParentVarname ($varname)
    {
        $this->parentVarnames[] = $varname;
    }

    function popParentVarname ()
    {
        return array_pop($this->parentVarnames);
    }

    function topParentVarname ()
    {
        return end($this->parentVarnames);
    }

    /**
     * Push a tag handler object on the stacks (one stack per tag class)
     */
    function pushHandler ($id, & $handler)
    {
    	if (!isset($GLOBALS['PSPhandler']))
            $GLOBALS['PSPhandler'] = array();
        $GLOBALS['PSPhandler'][$id][empty($GLOBALS['PSPhandler'][$id])?0:count($GLOBALS['PSPhandler'][$id])] = & $handler;
    }
    
    /**
     * Pop and return the top of the tag handler objects stack for the tag class with the given id
     */
    function & popHandler ($id)
    {
        $index = count($GLOBALS['PSPhandler'][$id])-1;
        $handler = & $GLOBALS['PSPhandler'][$id][$index];
        unset($GLOBALS['PSPhandler'][$id][$index]); 
        return $handler;
    }
    
    /**
     * Call startElement of the tag handler
     */
    function startElement ($namespace, $name, $attrs, $line)
    {
        $filename = $name.'Handler';
        $classname = $namespace."_".$filename;
        $path = $this->basedir.'tags/';
        $handlerFound = true;
        if (!class_exists($namespace."_".$filename))
        {
            if (file_exists($path.$namespace."/".$filename.'.php'))
            {
                require_once($path.$namespace."/".$filename.'.php');
            }
            else
            {
                if (!m2import('phpinchains/tags/'.$namespace."/".$filename.'.php', true)) {
                    die ("<b>PSPParser Fatal Error:</b> No handler found for tag <i>$namespace:$name</i>."); 
                }
            }
        }
        $handler = new $classname;
        if (method_exists($handler,'parentIn') && count($handler->parentIn()) > 0)
        {
            if (!in_array($this->valTop(), $handler->parentIn()))
            {
                print "<b>PSPParser:</b> ".$namespace.":".$name.' tag is not allowed in '.($this->valTop()?$this->valTop():'psp:root')." at line $line in file {$this->lexer->filename}!"; exit;
            }
        }
        $this->valstack[] = $namespace.":".$name;
        $handler->startElement($this, $attrs, $line, $this->lexer->filename);
        $this->pushHandler($namespace."_".$name, $handler);
        $this->currentNode['children'][] = array(
            'name' => $name,
            'children' => array(),
            'attrs' => $attrs,
            'parent' => $this->currentNode 
        );
        $this->currentNode = & end($this->currentNode['children']);
    }

    /**
     * Call endElement of the tag handler
     */
    function endElement ($namespace, $name)
    {
        $this->currentNode = & $this->currentNode['parent'];
        $handler = & $this->popHandler($namespace."_".$name);
        $handler->endElement($this);
        $this->valPop();
    }
/*    
    function startElement ($namespace, $name, $attrs, $line)
    {
        if (!isset($GLOBALS['PSPhandler'][$namespace."_".$name]) 
            || !is_object($GLOBALS['PSPhandler'][$namespace."_".$name]) 
            || !method_exists($GLOBALS['PSPhandler'][$namespace."_".$name], 'startElement'))
        {
            $classname = $name.'Handler';
            $path = $this->basedir.'tags/';
            if (file_exists($path.$namespace."/".$classname.'.php'))
            {
                require_once($path.$namespace."/".$classname.'.php');
            }
            else
            {
                m2import('phpinchains/tags/'.$namespace."/".$classname.'.php');
            }
            $classname = $namespace."_".$classname;
            $GLOBALS['PSPhandler'][$namespace."_".$name][] = new $classname;
        }
        if (method_exists($GLOBALS['PSPhandler'][$namespace."_".$name],'parentIn'))
        {
            if (!in_array($this->valTop(), $GLOBALS['PSPhandler'][$namespace."_".$name]->parentIn()))
            {
                print $namespace.":".$name.' tag is not allowed in '.($this->valTop()?$this->valTop():'psp:root')."!"; exit;
            }
        }
        $this->valstack[] = $namespace.":".$name;
        $GLOBALS['PSPhandler'][$namespace."_".$name]->startElement($this, $attrs, $line);
    }

    function endElement ($namespace, $name)
    {
        if (is_object($GLOBALS['PSPhandler'][$namespace."_".$name]) 
            && method_exists($GLOBALS['PSPhandler'][$namespace."_".$name], 'endElement'))
        {
            $GLOBALS['PSPhandler'][$namespace."_".$name]->endElement($this);
        }
        else
            echo "no handler exists for $name\n";
        $this->valPop();
    }
*/
    /**
     * Activate or deactivate surpression of append
     *
     * if activated append calls are ignored
     */
    function surpressAppend ($bool)
    {
        $this->surpressAppend = $bool;
    }

    /**
     * Activate or deactivate surpression of appending cdata
     *
     * if activated cData calls are ignored
     */
    function surpressCDataAppend ($bool)
    {
        $this->surpressCdataAppend = $bool;
    }

    /**
     * Process character data
     */ 
    function cData ($data)
    {
        if (!$this->surpressCdataAppend)
            $this->appendCode("\n\$out->write(\"".preg_replace("/\n/",'\n',preg_replace('/"/','\"',$data))."\");\n");
        //print $data;
    }

    function pushParent(& $parent)
    {
        $this->parentArray[] = & $parent;
    }

    function popParent()
    {
        if (count($this->parentArray) > 0)
            array_pop($this->parentArray);
    }

    function topParent()
    {
        if (count($this->parentArray) > 0)
            return $this->parentArray[count($this->parentArray) - 1];
    }

    function appendRequestableDataCode ($code)
    {
        if ($this->surpressAppend) return;
        $this->requestableData .= $code;
    }

    function getRequestableDataCode () {
        return $this->requestableData;
    }
    
    function appendCode ($code)
    {
        if ($this->surpressAppend) return;
        if ($this->capture)
            if (isset($this->captured[$this->capture]))
                $this->captured[$this->capture] .= $code;
            else
                $this->captured[$this->capture] = $code;
        $this->appCode .= $code;
    }
    
    function appendJSCode ($code, $escape = true)
    {
        if ($this->surpressAppend) return;
        if ($escape)
            $this->jsCode .= str_replace('"', '\"', $code);
        else
            $this->jsCode .= $code;
    }
    
    function appendValidationCode ($code)
    {
        if ($this->surpressAppend) return;
        $this->appValCode .= $code;
    }
    
    function getCode ()
    {
        return $this->appCode;
    }

    function getValidationCode ()
    {
        return $this->appValCode;
    }
    
    function getJSCode ()
    {
        return $this->jsCode;
    }
    
    function beginCaptureForm ($name)
    {
        $this->beginCapture($name);
    }

    function endCaptureForm ()
    {
        $this->endCapture();
    }

    function beginCapture ($name)
    {
        $this->capture = $name;
    }
    
    function clearCapture ($name)
    {
        unset($this->captured[$name]);
    }

    function getCaptured ($name)
    {
        return $this->captured[$name];
    }

    function endCapture ()
    {
        $this->capture = '';
    }
    
    function getFormsCode ()
    {
        $code = '';
        if (is_array($this->captured))
        foreach ($this->captured as $n => $v) 
            $code .= 'function & form'.$n.'(& $out, & $dispatcher, & $env, & $actionform) {'
                .$v.'}'."\n";
        return $code;
    }

    function getFormSelectionCode ()
    {
        $code = '';
        if (is_array($this->captured))
        foreach ($this->captured as $n => $v) 
            $code .= 'if ($actionform->get("__formselector__") == "'.$n.'")
{
    $this->form'.$n.'($out, $dispatcher, $env, $actionform);
    return $out;
}
';
        return $code;
    }
    
    function appendValidationField($formname, $fieldname, $validate, $validationParam) 
    {
        if ($this->surpressAppend) return;
        $this->addFormField($formname, $fieldname);
        $this->appValCode .= "\n        if (empty(\$fieldname) || \$fieldname == '$fieldname') \$this->validateField(\$env, \$actionform, '".$fieldname."', ".(isset($validationParam)?$validationParam:"''").', '.$validate.");\n";
    }
    
    function addFormField ($form, $field)
    {
        if (!isset($this->fieldMap[$form]))
            $this->fieldMap[$form] = array();
        if (!in_array($field, $this->fieldMap[$form]))
            $this->fieldMap[$form][] = $field;
    }
    
    function addFileField ($form, $field)
    {
        if (!isset($this->fileFieldMap[$form]))
            $this->fileFieldMap[$form] = array();
        if (!in_array($field, $this->fileFieldMap[$form]))
            $this->fileFieldMap[$form][] = $field;
    }
    
    function getFieldMap()
    {
        return $this->fieldMap;
    }

    function getFileFieldMap()
    {
        return $this->fileFieldMap;
    }

}

?>
